#!/usr/bin/env bash

include "test.sh"

_OBJECTV4_class_name=""
_OBJECTV4_cons_eval=""
_OBJECTV4_ctor_eval=""
_OBJECTV4_carg_eval=""
_OBJECTV4_dtor_eval=""
_OBJECTV4_dest_eval=""

function ObjectV4::_option::cargs()
{
    local ctor_id="0"
    for valname in $(sed "s@,@ @g" <<< "$1"); do
        _OBJECTV4_carg_eval+="
            eval \"__${_OBJECTV4_class_name}_\${!id}_${valname}=\${params[$((ctor_id++))]}\""
    done
}

function ObjectV4::_option::integer()
{
    local valname="${1%%,*}"
    if [ "$valname" == "" ]; then
        assert_fail "valname是空的"
        return 1
    fi

    local default="0"
    if [[ "$1" =~ (,) ]]; then
        default="${1#*,}"
    fi

    _OBJECTV4_cons_eval+="
        function ${_OBJECTV4_class_name}::${valname}()
        {
            local name=\"__${_OBJECTV4_class_name}_\${!1}_${valname}\"; shift
            if [ \$# == 0 ]; then
                echo \${!name}
            elif [ \$# == 1 ]; then
                if [ \$1 == inc ]; then
                    (( \$name ++ ))
                elif [ \$1 == dec ]; then
                    (( \$name -- ))
                else
                    eval \"\$name=\$1\"
                fi
            else
                assert_fail \"无法处理参数: \$*\"
            fi
        }
    "
    _OBJECTV4_ctor_eval+="
            eval \"__${_OBJECTV4_class_name}_\${!id}_${valname}=${default}\""
    _OBJECTV4_dtor_eval+="
            eval \"unset __${_OBJECTV4_class_name}_\${!id}_${valname}\""
    _OBJECTV4_dest_eval+="
            unset -f ${_OBJECTV4_class_name}::${valname}"
}

function ObjectV4::_option::string()
{
    local valname="${1%%,*}"
    if [ "$valname" == "" ]; then
        assert_fail "valname是空的"
        return 1
    fi

    local default=""
    if [[ "$1" =~ (,) ]]; then
        default="${1#*,}"
    fi

    _OBJECTV4_cons_eval+="
        function ${_OBJECTV4_class_name}::${valname}()
        {
            local name=\"__${_OBJECTV4_class_name}_\${!1}_${valname}\"; shift
            if [ \$# == 0 ]; then
                echo \${!name}
            elif [ \$# == 1 ]; then
                eval \"\${name}=\$1\"
            else
                assert_fail \"无法处理参数: \$*\"
            fi
        }
    "
    _OBJECTV4_ctor_eval+="
            eval \"__${_OBJECTV4_class_name}_\${!id}_${valname}='${default}'\""
    _OBJECTV4_dtor_eval+="
            eval \"unset __${_OBJECTV4_class_name}_\${!id}_${valname}\""
    _OBJECTV4_dest_eval+="
            unset -f ${_OBJECTV4_class_name}::${valname}"
}

function ObjectV4::_option::array()
{
    local valname="${1%%,*}"
    if [ "$valname" == "" ]; then
        assert_fail "valname是空的"
        return 1
    fi

    local default="()"
    if [[ "$1" =~ (,) ]]; then
        default="$*"
        default="${default#*,}"
    fi

    _OBJECTV4_cons_eval+="
        function ${_OBJECTV4_class_name}::${valname}()
        {
            local name=\"__${_OBJECTV4_class_name}_\${!1}_${valname}\"; shift
            if [ \$# == 1 ]; then
                if [ \$1 == len ]; then
                    eval \"echo \\\${#\$name[*]}\"
                elif [ \$1 == all ]; then
                    eval \"echo \\\${\$name[@]}\"
                else
                    eval \"echo \\\${\$name[\$1]}\"
                fi
            elif [ \$# == 2 ]; then
                if [ \$1 == push ]; then
                    eval \"\$name+=(\\\"\$2\\\")\"
                elif [ \$1 == erase ]; then
                    local object__option_array_swap=()
                    local object__option_array_len=0
                    eval \"object__option_array_len=\\\${#\$name[*]}\"
                    for (( object__option_array_i = 0; object__option_array_i < \$object__option_array_len; object__option_array_i++ )); do
                        local object__option_array_value
                        eval \"object__option_array_value=\\\"\\\${\$name[\\\$object__option_array_i]}\\\"\"
                        if [ \$object__option_array_value == \$2 ]; then
                            continue
                        fi
                        object__option_array_swap+=(\"\$object__option_array_value\")
                    done
                    eval \"\$name=(\${object__option_array_swap[*]})\"
                else
                    assert_fail \"没有该操作: \$*\"
                fi
            else
                assert_fail \"无法处理参数: \$*\"
            fi
        }
    "
    _OBJECTV4_ctor_eval+="
            eval \"__${_OBJECTV4_class_name}_\${!id}_${valname}=${default}\""
    _OBJECTV4_dtor_eval+="
            eval \"unset __${_OBJECTV4_class_name}_\${!id}_${valname}\""
    _OBJECTV4_dest_eval+="
            unset -f ${_OBJECTV4_class_name}::${valname}"
}

function ObjectV4::DefineClass()
{
    _OBJECTV4_class_name="$1"; shift

    local args=($*)
    local i
    for ((i = 0; i < ${#args[*]}; i++)); do
        local arg="${args[$i]}"
        while [[ "$arg" =~ (.*\(.*[^\)]$) ]]; do
            # 处理 '--array=arr,(1 2 3)'
            # 从 '--array=arr,(1' 变成完全体
            arg+=" ${args[$((++i))]}"
        done

        local key="${arg%%=*}"
        local val="${arg#*=}"
        key="${key:2}"
        call "ObjectV4::_option::$key" "$val"
    done

    eval "
        _${_OBJECTV4_class_name}_nextid=0
        _${_OBJECTV4_class_name}_refs=0
        function ${_OBJECTV4_class_name}()
        {
            if (( _${_OBJECTV4_class_name}_refs++ == 0 )); then
                $_OBJECTV4_cons_eval
            fi

            if [[ \$1 =~ (\{) ]]; then
                local define=\"\$*\"
                local symbol=\"\${define//\{*/}\"
                define=\"\${define:\$((\${#symbol} + 1)):-1}\"

                local params=()
                while (( \${#define} > 0 )); do
                    local param=\"\${define//,*/}\"
                    params+=(\"\${define//,*/}\")
                    define=\"\${define:\$((\${#param} + 2))}\"
                done
            else
                local symbol=\$1; shift
                local params=(\"\$@\")
            fi

            local id=_${_OBJECTV4_class_name}_nextid $_OBJECTV4_ctor_eval $_OBJECTV4_carg_eval
            (( \$symbol = _${_OBJECTV4_class_name}_nextid++ ))
            if declare -f ${_OBJECTV4_class_name}::${_OBJECTV4_class_name} 1>/dev/null; then
                ${_OBJECTV4_class_name}::${_OBJECTV4_class_name} \$symbol \"\${params[@]}\"
            fi
        }

        function ${_OBJECTV4_class_name}::destroy()
        {
            local id=\${!1}
            if declare -f ${_OBJECTV4_class_name}::~${_OBJECTV4_class_name} 1>/dev/null; then
                ${_OBJECTV4_class_name}::~${_OBJECTV4_class_name} \$1
            fi

            $_OBJECTV4_dtor_eval
            if (( --_${_OBJECTV4_class_name}_refs != 0 )); then
                return 0
            fi
            $_OBJECTV4_dest_eval
        }
    "
    unset _OBJECTV4_class_name
    unset _OBJECTV4_cons_eval
    unset _OBJECTV4_ctor_eval
    unset _OBJECTV4_dtor_eval
    unset _OBJECTV4_dest_eval
}

function ObjectV4::test1()
{
    ObjectV4::DefineClass OBJ "
        --cargs=url,name
        --string=url
        --string=name
        --array=actions
    "

    OBJ obj1{www.baidu.com, baidu}

    # cargs
    test::run_cmd OBJ::url obj1
    test::should_be www.baidu.com
    test::run_cmd OBJ::name obj1
    test::should_be baidu

    # string setter
    OBJ::url obj1 fanyi.youdao.com
    test::run_cmd OBJ::url obj1
    test::should_be fanyi.youdao.com

    # array operation
    test::run_cmd OBJ::actions obj1 len
    test::should_be 0

    ## push 123
    OBJ::actions obj1 push 123
    test::run_cmd OBJ::actions obj1 len
    test::should_be 1
    test::run_cmd OBJ::actions obj1 0
    test::should_be 123

    # other object
    OBJ obj2{www.baidu.com, baidu}
    test::run_cmd OBJ::name obj1
    test::should_be baidu
    test::run_cmd OBJ::url obj1
    test::should_be fanyi.youdao.com

    # other object destroy
    OBJ::destroy obj2
    test::run_cmd OBJ::name obj1
    test::should_be baidu
    test::run_cmd OBJ::url obj1
    test::should_be fanyi.youdao.com
    OBJ::actions obj1 push 456
    OBJ::actions obj1 push 789
    test::run_cmd OBJ::actions obj1 len
    test::should_be 3
    test::run_cmd OBJ::actions obj1 0
    test::should_be 123
    test::run_cmd OBJ::actions obj1 1
    test::should_be 456
    test::run_cmd OBJ::actions obj1 2
    test::should_be 789

    OBJ::destroy obj1
}
test::register_test ObjectV4::test1

function ObjectV4::test2()
{
    ObjectV4::DefineClass OBJ "
        --array=actions
    "

    OBJ obj{www.baidu.com, baidu}
    OBJ::actions obj push 123
    OBJ::actions obj push 456
    OBJ::actions obj push 789
    test::run_cmd OBJ::actions obj len
    test::should_be 3
    test::run_cmd OBJ::actions obj 1
    test::should_be 456

    # erase
    OBJ::actions obj erase 456
    test::run_cmd OBJ::actions obj len
    test::should_be 2
    test::run_cmd OBJ::actions obj 1
    test::should_be 789

    OBJ::destroy obj
}
test::register_test ObjectV4::test2

function ObjectV4::test3()
{
    ObjectV4::DefineClass OBJ "
        --integer=index
    "

    OBJ obj{}
    test::run_cmd OBJ::index obj
    test::should_be 0

    OBJ::index obj inc
    test::run_cmd OBJ::index obj
    test::should_be 1

    OBJ::index obj inc
    test::run_cmd OBJ::index obj
    test::should_be 2

    OBJ::index obj dec
    test::run_cmd OBJ::index obj
    test::should_be 1

    OBJ::index obj 3
    test::run_cmd OBJ::index obj
    test::should_be 3

    OBJ::destroy obj
}
test::register_test ObjectV4::test3

function ObjectV4::test4()
{
    ObjectV4::DefineClass OBJ "
        --integer=index,-1
        --string=str,default
    "  "--array=arr,(1 2 3)"

    OBJ obj{}

    test::run_cmd OBJ::index obj
    test::should_be -1
    test::run_cmd OBJ::str obj
    test::should_be default
    test::run_cmd OBJ::arr obj len
    test::should_be 3
    test::run_cmd OBJ::arr obj 0
    test::should_be 1
    test::run_cmd OBJ::arr obj 1
    test::should_be 2
    test::run_cmd OBJ::arr obj 2
    test::should_be 3

    OBJ::destroy obj
}
test::register_test ObjectV4::test4
